package mobilefeel.dto;

import java.util.ArrayList;
import java.util.List;

/**
 * Object used to report tactile movement events over the Android device's
 * touchscreen. This class holds movements relative to their view.
 * <br/><br/>
 * Pointer coordinates specify view X/Y coordinates. Each complete gesture is
 * represented by a sequence of touch events with actions that describe
 * pointer state transitions and movements. A gesture starts with an
 * {@link #ACTION_DOWN} touch event that provides the location of the first
 * pointer down. As each additional pointer that goes down or up, a {@link 
 * #ACTION_POINTER_DOWN} or {@link #ACTION_POINTER_UP} touch event will be
 * generated accordingly. Pointer movements are described by {@link
 * #ACTION_MOVE} touch events. Finally, a gesture ends either when the final
 * pointer goes up as represented by a {@link #ACTION_UP} touch event, when the
 * gesture is canceled with {@link #ACTION_CANCEL} or when the gesture goes
 * outside the bounds of the view, generating an {@link #ACTION_OUTSIDE} event.
 * <br/><br/>
 * Touch events always report movements for all pointers at once. The number
 * of pointers only ever changes by one as individual pointers go up and down,
 * except when the gesture is canceled.
 * <br/><br/>
 * The order in which individual pointers appear within a touch event can
 * change from one event to the next. Use the {@link #getPointerId(int)} method
 * to obtain a pointer id to track pointers across touch events in a gesture.
 * Then for successive events, use the {@link #findPointerIndex(int)} method to
 * obtain the pointer index for a given pointer id in that touch event.
 * <br/><br/>
 * For efficiency, touch events with {@link #ACTION_MOVE} or {@link 
 * #ACTION_OUTSIDE} may batch together multiple movement samples within a
 * single object. The most current pointer coordinates are available using
 * {@link #getCurrentById(int)} and {@link #getCurrentByIndex(int)}. Earlier
 * coordinates within the batch are accessed using {@link
 * #getHistoricalById(int, int)} and {@link #getHistoricalByIndex(int, int)}.
 * The coordinates are "historical" only insofar as they are older than the
 * current coordinates in the batch; however, they are still distinct from any
 * other coordinates reported in prior touch events. To process all coordinates
 * in the batch in time order, first consume the historical coordinates then
 * consume the current coordinates.
 * <br/><br/>
 * For Android developers used to working with the MotionEvent implementation,
 * TouchEvent will be familiar. However, unlike Android's multitouch events
 * implementation, MobileFeel's implementation allows developers to capture
 * multitouch events from several views simultaneously, making the programmer's
 * job easier and allowing for a richer multitouch interactivity. Touch events
 * will be split across views. In Android's implementation, only a single view
 * could accept touch events at one time.
 */
public class TouchEvent extends ViewEvent{

	private static final long serialVersionUID = 1L;

	private List<Integer> pointers;
	private List<ScreenCoordinates> current;
	private List<List<ScreenCoordinates>> historical;
	private int action;
	private int actionPointer;
	private long eventTime;
	private List<Long> historicalEventTime;		
	
	/**
	 * Constant for {@link #getAction()}: A pressed gesture has started, the
	 * motion contains the initial starting location.
	 */
	public static final int ACTION_DOWN = 0;
	/**
	 * Constant for {@link #getAction()}: A pressed gesture has finished, the
	 * motion contains the final release location as well as any intermediate
	 * points since the last down or move event.
	 */
	public static final int ACTION_UP = 1;
	/**
	 * Constant for {@link #getAction()}: A change has happened during a press
	 * gesture (between {@link #ACTION_DOWN} and {@link #ACTION_UP}). The
	 * motion contains the most recent point, as well as any intermediate
	 * points since the last down or move event.
	 */
	public static final int ACTION_MOVE = 2;
	/**
	 * Constant for {@link #getAction()}: The current gesture has been aborted.
	 * You should treat this as an up event, but not perform any action that
	 * you normally would.
	 */
	public static final int ACTION_CANCEL = 3;
	/**
	 * Constant for {@link #getAction()}: The current gesture has exited the
	 * bounds of the view.
	 */
	public static final int ACTION_OUTSIDE = 4;
	/**
	 * Constant for {@link #getAction()}: A non-primary pointer has gone down.
	 * Check {@link #getActionPointer()} to know which pointer has changed.
	 */
	public static final int ACTION_POINTER_DOWN = 5;
	/**
	 * Constant for {@link #getAction()}: A non-primary pointer has gone up.
	 * Check {@link #getActionPointer()} to know which pointer has changed.
	 */
	public static final int ACTION_POINTER_UP = 6;

	/**
	 * Sole constructor.
	 */
	public TouchEvent() {
		pointers = new ArrayList<Integer>();
		current = new ArrayList<ScreenCoordinates>();
		historical = new ArrayList<List<ScreenCoordinates>>();
		historicalEventTime = new ArrayList<Long>();
	}
	
	/**
	 * Returns the number of pointers of data contained in this event. Always
	 * >= 1.
	 * 
	 * @return an integer value containing the number of pointers
	 */
	public int getPointerCount() {
		return pointers.size();
	}
	
	/**
	 * Returns the number of historical points in this event. These are
	 * movements that have occurred between this event and the previous event.
	 * This only applies to {@link #ACTION_MOVE} and {@link #ACTION_OUTSIDE}
	 * events -- all other actions will have a size of 0.
	 * 
	 * @return an integer value containing the number of historical points
	 */
	public int getHistorySize() {
		return historical.size();
	}
	
	/**
	 * Returns the current screen coordinates of this event for the given
	 * pointer ID.
	 * 
	 * @param pointerId the ID of pointer to retrieve
	 * @return a {@link ScreenCoordinates} instance containing the coordinates
	 * for the given pointer
	 */
	public ScreenCoordinates getCurrentById(int pointerId) {
		return current.get(findPointerIndex(pointerId));
	}
	
	/**
	 * Returns the current screen coordinates of this event for the given
	 * pointer index.
	 * 
	 * @param pointerIndex the index of pointer to retrieve. Value may be from
	 * 0 (the first pointer that is down) to {@link #getPointerCount()}-1
	 * @return a {@link ScreenCoordinates} instance containing the coordinates
	 * for the given pointer
	 */
	public ScreenCoordinates getCurrentByIndex(int pointerIndex) {
		return current.get(pointerIndex);
	}
	
	/**
	 * Returns historical screen coordinates, that occurred between this event
	 * and the previous event for the given pointer ID. Only applies to {@link
	 * #ACTION_MOVE} or {@link #ACTION_OUTSIDE} events.
	 * 
	 * @param pointerId the ID of pointer to retrieve.
	 * @param pos the position of the historical value to return. Must be less
	 * than {@link #getHistorySize()}
	 * @return a {@link ScreenCoordinates} instance containing the coordinates
	 * for the given pointer
	 */
	public ScreenCoordinates getHistoricalById(int pointerId, int pos) {
		return historical.get(pos).get(findPointerIndex(pointerId));
	}
	
	/**
	 * Returns historical screen coordinates, that occurred between this event
	 * and the previous event for the given pointer index. Only applies to
	 * {@link #ACTION_MOVE} or {@link #ACTION_OUTSIDE} events.
	 * 
	 * @param pointerIndex the index of pointer to retrieve. Value may be from
	 * 0 (the first pointer that is down) to {@link #getPointerCount()}-1
	 * @param pos the position of the historical value to return. Must be less
	 * than {@link #getHistorySize()}
	 * @return a {@link ScreenCoordinates} instance containing the coordinates
	 * for the given pointer
	 */
	public ScreenCoordinates getHistoricalByIndex(int pointerIndex, int pos) {
		List<ScreenCoordinates> list = historical.get(pos);
		if (list != null)
			return list.get(pointerIndex);
		return null;
	}
	
	/**
	 * Returns the time at which a historical movement occurred between this
	 * event and the previous event. Only applies to {@link #ACTION_MOVE} or
	 * {@link #ACTION_OUTSIDE} events.
	 * 
	 * @param pos the position of the historical value to return. Must be less
	 * than {@link #getHistorySize()} 
	 * @return a long value containing the historical event time (in
	 * milliseconds)
	 */
	public long getHistoricalEventTime(int pos) {
		if (pos < historicalEventTime.size())
			return historicalEventTime.get(pos);
		return -1L;
	}

	/**
	 * Returns the time at which this specific event was generated.
	 * 
	 * @return a long value containing the  event time (in milliseconds)
	 */
	public long getEventTime() {
		return eventTime;
	}
	
	/**
	 * Adds a set of current screen coordinates for the given pointer ID.
	 * 
	 * @param pointer the pointer ID
	 * @param sc a {@link ScreenCoordinates} instance containing X and Y
	 * coordinates information for the given pointer
	 */
	public void addCurrent(int pointer, ScreenCoordinates sc) {
		if (!pointers.contains(pointer))
			pointers.add(pointer);
		current.add(findPointerIndex(pointer), sc);
	}
	
	/**
	 * Adds a set of historical screen coordinates for the given pointer ID.
	 * 
	 * @param pointer the pointer ID
	 * @param pos the position of the historical value
	 * @param sc a {@link ScreenCoordinates} instance containing X and Y
	 * coordinates information for the given pointer
	 */
	public void addHistorical(int pointer, int pos, ScreenCoordinates sc) {
		if (!pointers.contains(pointer))
			pointers.add(pointer);
		if (pos >= historical.size())
			historical.add(pos, new ArrayList<ScreenCoordinates>());
		historical.get(pos).add(findPointerIndex(pointer), sc);
	}
	
	/**
	 * Adds the event time for a given historical position.
	 * 
	 * @param pos the position of the historical value
	 * @param time a long value containing the historical event time (in
	 * milliseconds)
	 */
	public void addHistoricalEventTime(int pos, long time) {
		historicalEventTime.add(pos, time);
	}

	/**
	 * Sets the event time.
	 * 
	 * @param eventTime a long value containing the event time (in
	 * milliseconds)
	 */
	public void setEventTime(long eventTime) {
		this.eventTime = eventTime;
	}
	
	/**
	 * Sets the event's action. Must be one of either {@link #ACTION_DOWN},
	 * {@link #ACTION_UP}, {@link #ACTION_MOVE}, {@link #ACTION_CANCEL},
	 * {@link #ACTION_OUTSIDE}, {@link #ACTION_POINTER_DOWN} or {@link
	 * #ACTION_POINTER_UP}.
	 * 
	 * @param action an integer value containing the kind of action being
	 * performed
	 */
	public void setAction(int action) {
		this.action = action;
	}

	/**
	 * Returns the event's action. Must be one of either {@link #ACTION_DOWN},
	 * {@link #ACTION_UP}, {@link #ACTION_MOVE}, {@link #ACTION_CANCEL},
	 * {@link #ACTION_OUTSIDE}, {@link #ACTION_POINTER_DOWN} or {@link
	 * #ACTION_POINTER_UP}. For the last two, consider using {@link
	 * #getActionPointer()} to retrieve the pointer involved in the action.
	 * 
	 * @return an integer value containing the kind of action being
	 * performed
	 */
	public int getAction() {
		return action;
	}
	
	/**
	 * Returns the pointer identifier associated with a particular pointer data
	 * index is this event. The identifier tells you the actual pointer number
	 * associated with the data, accounting for individual pointers going up
	 * and down since the start of the current gesture.
	 * 
	 * @param pointerIndex the index of pointer to retrieve. Value may be from
	 * 0 (the first pointer that is down) to {@link #getPointerCount()}-1
	 * @return either the ID of the pointer or -1 if there is no data available
	 * for that pointer index
	 */
	public int getPointerId(int pointerIndex) {
		if (pointerIndex < pointers.size())
			return pointers.get(pointerIndex);
		return -1;
	}
	
	/**
	 * Given a pointer identifier, find the index of its data in the event.
	 * 
	 * @param pointerId the identifier of the pointer to be found
	 * @return either the index of the pointer or -1 if there is no data
	 * available for that pointer identifier
	 */
	public int findPointerIndex(int pointerId) {
		for (int i = 0; i < pointers.size(); i++)
			if (pointers.get(i) == pointerId)
				return i;
		return -1;
	}

	/**
	 * Returns the associated pointer ID for {@link #ACTION_POINTER_DOWN} and
	 * {@link #ACTION_POINTER_UP} actions. It may be used to get information
	 * about the pointer that has gone down or up.
	 * 
	 * @return the action's pointer ID
	 */
	public int getActionPointer() {
		return actionPointer;
	}

	/**
	 * Sets the associated pointer ID for {@link #ACTION_POINTER_DOWN} and
	 * {@link #ACTION_POINTER_UP} actions.
	 * 
	 * @param actionPointer the action's pointer ID
	 */
	public void setActionPointer(int actionPointer) {
		this.actionPointer = actionPointer;
	}

}